package com.uxsino.simo.networkentity;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import com.uxsino.commons.cache.Cache;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.JsonNode;
import com.uxsino.commons.logicSelector.ISelector;
import com.uxsino.commons.logicSelector.SelectorFactory;
import com.uxsino.commons.logicSelector.SelectorGroup;
import com.uxsino.commons.utils.config.IResourceWalker;
import com.uxsino.commons.utils.config.PropElement;
import com.uxsino.commons.utils.config.PropJsonDocument;
import com.uxsino.commons.utils.config.PropXMLDocument;
import com.uxsino.simo.indicator.Indicator;
import com.uxsino.simo.indicator.IndicatorNamespace;
import com.uxsino.simo.selector.EntitySelectorContext;
import com.uxsino.simo.utils.ConfigLoadingContext;

import reactor.core.publisher.EmitterProcessor;
import reactor.core.publisher.Flux;

/**
 * 
 * A collection of network entities (aka NE) (see {@link EntityInfo} and
 * corresponding information include: {@link EntityClass}. classes of these NE
 * {@link SelectorGroup}. predefined selectors to chose NE from this collection
 * 
 */
public class EntityDomain {
    public static Logger logger = LoggerFactory.getLogger(EntityDomain.class);

    private String collectorId;

    private boolean isMultiCollector;

    private Map<String, EntityInfo> entities;

    private Map<String, EntityClass> entityClasses;

    private Map<String, EntityClass> entityClasseParents;

    private Map<String, SelectorGroup<EntityInfo>> namedEntitySelectors;

    private SelectorFactory<EntityInfo> selectorFactory;

    private EmitterProcessor<EntityInfo> newEntityProcessor = EmitterProcessor.<EntityInfo> create();

    private EmitterProcessor<EntityInfo> removeEntityProcessor = EmitterProcessor.<EntityInfo> create();

    public EntityDomain() {
        entities = new HashMap<String, EntityInfo>();
        entityClasseParents = new HashMap<String, EntityClass>();

        entityClasses = new HashMap<String, EntityClass>();
        namedEntitySelectors = new HashMap<String, SelectorGroup<EntityInfo>>();
        selectorFactory = new SelectorFactory<EntityInfo>();
    }

    /**
     * load entities from a list
     * 
     * @param entityInfos
     */
    public void loadNetworkEntities(List<EntityInfo> entityInfos) {
        if (isMultiCollector) {
            for (EntityInfo entityInfo : entityInfos) {
                // logger.info("entityInfo collectorId is {}, entityDomain
                // collectorId is {}", entityInfo.collectorId,
                // collectorId);
                if (collectorId.equals(entityInfo.collectorId)) {
                    entities.put(entityInfo.id, entityInfo);
                }
            }
        } else {
            for (EntityInfo entityInfo : entityInfos) {
                entities.put(entityInfo.id, entityInfo);
            }
        }
    }

    /**
     * load entity classes from a list
     * 
     * @param allEnntityClasses
     */
    public void loadEntityClasses(List<EntityClass> allEnntityClasses) {
        for (EntityClass entityClass : allEnntityClasses) {
            entityClasses.put(entityClass.id, entityClass);
        }
        for (EntityClass entityClass : allEnntityClasses) {
            entityClasseParents.put(entityClass.getParentId(), entityClasses.get(entityClass.getParentId()));
        }
    }

    /**
     * get the parent class of a entity.
     * 
     * @param entityClass
     * @return
     */
    public EntityClass getParent(EntityClass entityClass) {
        return entityClasseParents.get(entityClass.getParentId());
    }

    /**
     * load predefined NE selector
     * 
     * @param resourceWalkers
     * @return
     */
    public ConfigLoadingContext loadSelectors(IResourceWalker... resourceWalkers) {
        ConfigLoadingContext lctxt = new ConfigLoadingContext(EntityDomain.class);
        Arrays.stream(resourceWalkers).filter(Objects::nonNull).forEach(walker -> {
            try {
                walker.forEach(url -> {
                    String srcName = url.getFile();
                    lctxt.setCurrentSource(srcName);
                    InputStream is = null;

                    try {
                        is = url.openStream();
                        loadSelectorsFromXmlStream(is, srcName);
                    } catch (IllegalArgumentException e) {
                        lctxt.error("", "error loading indicator");
                    } catch (IOException e) {
                        lctxt.error("", "error reading indicator file", e);
                    } finally {
                        if (is != null) {
                            try {
                                is.close();
                            } catch (IOException e) {
                            }
                        }
                    }
                });
            } catch (IOException e) {
                logger.error("error loading indicators", e);
            }
        });
        return lctxt;
    }

    public void solveSelector(ISelector<EntityInfo> selector) {
        selector.solveReference(this::getEntitySelector);
    }

    public void loadSelectorsFromXmlFile(File xmlFile, String selectorFile) {
        PropXMLDocument doc = new PropXMLDocument(xmlFile, selectorFile);

        for (PropElement eSelector : doc.getElements("selector")) {
            String selectorName = eSelector.getProp("name");
            SelectorGroup<EntityInfo> sel = createUnresolvedSelectorGroup(eSelector);
            sel.name = selectorName;
            namedEntitySelectors.put(selectorName, sel);
        }

        namedEntitySelectors.values().forEach(r -> r.solveReference(this::getEntitySelector));
    }

    public void loadSelectorsFromXmlStream(InputStream is, String selectorFile) {
        PropXMLDocument doc = new PropXMLDocument(is, selectorFile);

        for (PropElement eSelector : doc.getElements("selector")) {
            String selectorName = eSelector.getProp("name");
            SelectorGroup<EntityInfo> sel = createUnresolvedSelectorGroup(eSelector);
            sel.name = selectorName;
            namedEntitySelectors.put(selectorName, sel);
        }

        namedEntitySelectors.values().forEach(r -> r.solveReference(this::getEntitySelector));
    }

    public void loadSelectorsFromJson(JsonNode node) {
        PropJsonDocument doc = new PropJsonDocument(node);
        for (PropElement eSelector : doc.getElements("selector")) {
            String selectorName = eSelector.getProp("name");
            SelectorGroup<EntityInfo> sel = createUnresolvedSelectorGroup(eSelector);
            sel.name = selectorName;
            namedEntitySelectors.put(selectorName, sel);
        }

        namedEntitySelectors.values().forEach(r -> r.solveReference(this::getEntitySelector));

    }

    private SelectorGroup<EntityInfo> createUnresolvedSelectorGroup(PropElement egroup) {
        return selectorFactory.createSelectorGroup(egroup);
    }

    public SelectorGroup<EntityInfo> createSelectorGroupFromXMLString(String xml, String sourceName) {
        PropXMLDocument doc = new PropXMLDocument(xml, sourceName, PropXMLDocument.SOURCE_TYPE.STRING);
        return createSelectorGroup(doc.getRootElement());
    }

    public SelectorGroup<EntityInfo> createSelectorGroup(PropElement egroup) {
        SelectorGroup<EntityInfo> g = createUnresolvedSelectorGroup(egroup);
        g.solveReference(this::getEntitySelector);
        return g;
    }

    public void addEntity(EntityInfo entity) {
        Objects.requireNonNull(entity);
        entities.put(entity.id, entity);
        newEntityProcessor.onNext(entity);
    }

    public void addEntityClass(EntityClass entityClass) {
        entityClasses.put(entityClass.id, entityClass);
    }

    public EntityClass getEntityClassById(String classId) {
        return entityClasses.get(classId);
    }

    public EntityInfo getEntity(String entityId) {
        return entities.get(entityId);
    }

    public EntityInfo deleteEntity(String entityId) {
        EntityInfo ne = entities.remove(entityId);
        if (ne != null) {
            if(ne.protocols != null){
                ne.protocols.keySet().forEach(p->{
                    Pair<String, String> key = Pair.of("NE_PROTOCOL_TEST_STATE_"+entityId, p);
                    Cache.remove(key);
                });
            }
            removeEntityProcessor.onNext(ne);
        }
        return ne;
    }

    public void clearEntites() {
        entities.clear();
    }

    public List<EntityInfo> getEntities() {
        return new ArrayList<>(entities.values());
    }

    public Iterator<EntityInfo> listEntities() {
        return entities.values().iterator();
    }

    public Iterator<EntityClass> listEntityClasses() {
        return entityClasses.values().iterator();
    }

    public EntityInfo[] dumpEntities() {
        return entities.values().toArray(new EntityInfo[entities.size()]);
    }

    public int getEntityCount() {
        return entities.size();
    }

    public EntityClass[] dumpEntityClasses() {
        return entityClasses.values().toArray(new EntityClass[entityClasses.size()]);
    }

    public Map<String, SelectorGroup<EntityInfo>> dumpSelectors() {
        return namedEntitySelectors;
    }

    public boolean entityClassIsTypeOf(String srcClassName, String destClassName) {
        EntityClass src = getEntityClassById(srcClassName);
        EntityClass dest = getEntityClassById(destClassName);

        if (src != null && dest != null) {
            return src.isTypeOf(dest);
        }
        return false;
    }

    public ISelector<EntityInfo> getEntitySelector(String selectorName) {
        return namedEntitySelectors.get(selectorName);
    }

    public EntityInfo[] selectEntitiesFromXMLString(String xmlSelector, String sourceName) {
        SelectorGroup<EntityInfo> selectorGroup = createSelectorGroupFromXMLString(xmlSelector, sourceName);
        if (selectorGroup == null) {
            return null;
        }

        return selectEntities(selectorGroup);
    }

    public EntityInfo[] selectEntities(SelectorGroup<EntityInfo> selectors) {
        ArrayList<EntityInfo> list = new ArrayList<>();

        EntitySelectorContext context = new EntitySelectorContext(this);
        for (EntityInfo entity : entities.values()) {
            context.setObject(entity);
            if (selectors.accept(context)) {
                list.add(entity);
            }
        }
        return list.toArray(new EntityInfo[list.size()]);

    }

    public long getEntityLastReleaseTime() {
        return Collections.max(entities.values(), Comparator.comparing(e -> e.releaseTime)).releaseTime;
    }

    public long getEntityClassLastReleaseTime() {
        return Collections.max(entityClasses.values(), Comparator.comparing(e -> e.releaseTime)).releaseTime;

    }

    public String[] getAllMatchingSelectorGroupNamesForEntity(EntityInfo ne) {
        ArrayList<String> result = new ArrayList<>();
        EntitySelectorContext context = new EntitySelectorContext(this);

        context.setObject(ne);
        for (Map.Entry<String, SelectorGroup<EntityInfo>> entry : namedEntitySelectors.entrySet()) {
            if (entry.getValue().accept(context)) {
                result.add(entry.getKey());
            }
        }
        return result.toArray(new String[result.size()]);
    }

    public void setCollectorId(String collectorId) {
        this.collectorId = collectorId;
    }

    public void setIsMultiCollector(boolean isMulti) {
        isMultiCollector = isMulti;
    }

    public String[] getAllMatchingSelectorGroupNamesForEntityClassId(String clsId) {
        EntityClass cls = this.getEntityClassById(clsId);
        if (cls == null)
            return new String[0];

        return getAllMatchingSelectorGroupNamesForEntityClass(cls);
    }

    public String[] getAllMatchingSelectorGroupNamesForEntityClass(EntityClass cls) {
        ArrayList<String> result = new ArrayList<>();
        EntitySelectorContext context = new EntitySelectorContext(this);

        for (EntityInfo ne : entities.values()) {
            if (ne.getEntityClass().isTypeOf(cls)) {
                context.setObject(ne);
                for (Map.Entry<String, SelectorGroup<EntityInfo>> entry : namedEntitySelectors.entrySet()) {
                    if (entry.getValue().accept(context)) {
                        result.add(entry.getKey());
                    }
                }
            }
        }

        return result.toArray(new String[result.size()]);
    }

    public Indicator[] getWarnIndicatorForEntity(EntityInfo ne, IndicatorNamespace ns) {
        String[] groupNames = getAllMatchingSelectorGroupNamesForEntity(ne);

        return ns.getIndicatorsForWarnGroupNames(groupNames);
    }

    public Indicator[] getWarnIndicatorForEntityClass(EntityClass cls, IndicatorNamespace ns) {
        String[] groupNames = getAllMatchingSelectorGroupNamesForEntityClass(cls);

        return ns.getIndicatorsForWarnGroupNames(groupNames);

    }

    public Indicator[] getWarnIndicatorForEntityClassId(String clsId, IndicatorNamespace ns) {
        String[] groupNames = getAllMatchingSelectorGroupNamesForEntityClassId(clsId);

        return ns.getIndicatorsForWarnGroupNames(groupNames);

    }

    public void updateEntity(EntityInfo entityInfo) {
        deleteEntity(entityInfo.id);
        addEntity(entityInfo);
    }

    public Flux<EntityInfo> getNewEntityFlux() {
        return newEntityProcessor;
    }

    public Flux<EntityInfo> getRemoveEntityFlux() {
        return removeEntityProcessor;
    }
}
